package screen;

import gameClass.MapChunk;
import gameClass.PlayerCharacter;
import gameClass.TileMap;
import gameTypes.Drawable;
import gameTypes.DrawableObject;
import gameTypes.DrawableType;
import gameTypes.Vector2;
import globals.Global;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;

import network.DataPackage;
import attack.PlaceTiles;

/**
 * @author Ben Elliott
 */
public class GameScreen implements Drawable {

	/* Technical Variables */
	private boolean loaded, loading;
	private int numHorizontalTiles;
	private int numVerticalTiles;
	private int tileSize;
	private TileMap tileMap;
	private float timeToDelay, maxTimeToDelay = 200/*milliseconds*/;
	
	/* Game Variables */
	PlayerCharacter player;
	List<DrawableObject> drawableObjects;
	Map<Vector2, MapChunk> chunks;
	
	// test
	int xchunk, ychunk;
	
	public GameScreen() {
		loaded = false;
		loading = false;
	}
	
	public void Load() {
		loading = true;
		
		// Get the tileSet
		try {
			// Get tileSheet
			URL rsrcUrl = Thread.currentThread().getContextClassLoader().getResource("tileSet.png");
			Global.Instance().tileSheet = ImageIO.read(rsrcUrl);
		} catch (IOException e) {
			// TODO: handle major error - exit
			System.exit(-1);
		}
		
		// Load per tiles sub images
		Global.Instance().tileImages = new ArrayList<BufferedImage>();
		int numTilesX = Global.Instance().tileSheet.getWidth()/Global.Instance().tileSize;
		int numTilesY = Global.Instance().tileSheet.getHeight()/Global.Instance().tileSize;
		for (int x =0; x < numTilesX; x++) {
			for (int y =0; y < numTilesY; y++) {
				Global.Instance().tileImages.add(
						Global.Instance().tileSheet.getSubimage(
								x * Global.Instance().tileSize,
								y * Global.Instance().tileSize,
								Global.Instance().tileSize, Global.Instance().tileSize));
			}
		}
				
		// Load tiles and map
		tileMap = new TileMap(100,100);
		chunks = new LinkedHashMap<Vector2, MapChunk>(16, 0.75f, true) {
			@Override
			protected boolean removeEldestEntry(
					java.util.Map.Entry<Vector2, MapChunk> eldest) {
				return size() > Global.Instance().MAX_ENTRIES;
			}
	    };
		Global.Instance().chunks = this.chunks;
		
		//chunks.put(new Vector2(0,0), ch0x0);
		
		// Create a global reference
		Global.Instance().tileMap = tileMap;
		tileSize = Global.Instance().tileSize;
		numHorizontalTiles = Global.Instance().winWidth / tileSize + 2;
		numVerticalTiles = Global.Instance().winHeight / tileSize + 2;
		
		// Get the tileSet
		try {
			// Get sprite sheet
			URL rsrcUrl = Thread.currentThread().getContextClassLoader().getResource("spriteSheet.png");
		    Global.Instance().playerSheet = ImageIO.read(rsrcUrl);
		} catch (IOException e) {
			// TODO: Major error - exit
			System.exit(-1);
		}
		
		// Initialize
		drawableObjects = new ArrayList<DrawableObject>();
		// Initialize character info
		Global.Instance().player = new PlayerCharacter(Global.Instance().username, true, 16, 128);
		this.player = Global.Instance().player;
		Global.Instance().leftClick = new PlaceTiles(new int[1][1]);
		drawableObjects.add(player);
		//drawables.add(new PlayerCharacter("Guy", false, 16, 32));
		
		
		loaded = true;
	}
	
	@Override
	public void Update(float elapsed) {
		float newX = player.location.x - Global.Instance().winWidth/2;
		float newY = player.location.y - Global.Instance().winHeight/2;
		// NO CLAMP -- DYNAMIC LOADING LEVELS NEW
		//if (newX/tileSize + winTileWidth < tileMap.getNumTilesX()) // Clamp far right
			Global.Instance().camera.setX(newX);
		//if (newY/tileSize + winTileHeight < tileMap.getNumTilesY()) // Clamp far bottom
			Global.Instance().camera.setY(newY);
		
		// Get info on other players
		for (DataPackage dp : Global.Instance().others) {
			if (dp.username.length() > 0 && dp.username.compareTo(Global.Instance().username) != 0) {
				// Find the NPC for the other player
				boolean found = false;
				for (int i = 0; i < drawableObjects.size(); i++) {
					// Check if the drawable is a player Character
					if (drawableObjects.get(i).drawableType == DrawableType.PlayerCharacter) {
						PlayerCharacter p = (PlayerCharacter)drawableObjects.get(i);

						if (p.name.compareTo(dp.username) == 0) {
							// Update info
							p.location.x = dp.x;
							p.location.y = dp.y;
							p.xSpeed = dp.xSpeed;
							p.ySpeed = dp.ySpeed;
							
							found = true;
							p.timeout = 0;
							break;
						}
					}
				}
				
				// New player found
				if (!found) {
					PlayerCharacter pc = new PlayerCharacter(dp.username);
					pc.location.x = 32;
					pc.location.y = 32;
					pc.xSpeed = dp.xSpeed;
					pc.ySpeed = dp.ySpeed;
					pc.friendly = true;
					drawableObjects.add(pc);
				}
			}
		}
		
		// Clear received data cache
		Global.Instance().others.clear();
		
		// Remove player timeout
		Global.Instance().player.timeout = 0;
		
		// Update drawables
		for (int i = 0; i < drawableObjects.size(); i++) {
			// Check if the drawable is a player Character
			if (drawableObjects.get(i).drawableType == DrawableType.PlayerCharacter) {
				PlayerCharacter p = (PlayerCharacter)drawableObjects.get(i);
				
				// Check for timeout
				if (p.timeout > 5000) {
					drawableObjects.remove(i);
					i--;
				}
			}

			// Update for every drawable
			drawableObjects.get(i).Update(elapsed);
		}
		
		/*  Delayed update for low priority  */
		timeToDelay += elapsed;
		if (timeToDelay > maxTimeToDelay) {
			timeToDelay = 0;
			delayedUpdate(elapsed);
		}
				
			
		/* ********** Input ******************************************/	
		if (Global.Instance().input.isKeyDown('W'))
			player.ySpeed -= 0.5f;
		else if (Global.Instance().input.isKeyDown('S'))
			player.ySpeed += 0.5f;
		if (Global.Instance().input.isKeyDown('A'))
			player.xSpeed -= 0.5f;
		else if (Global.Instance().input.isKeyDown('D'))
			player.xSpeed += 0.5f;
		
		if (Global.Instance().input.isMouseClicked()) {
			Global.Instance().leftClick.Action();
		}
	}

	private void delayedUpdate(float elapsed) {
		/*  Sort to draw in the correct order every interval  */
		Collections.sort(drawableObjects, new Comparator<DrawableObject>() {
			@Override
			public int compare(DrawableObject o1, DrawableObject o2) {
				if (o1.location.y > o2.location.y) {
					return 1;
				} else return -1;
			}});
		
		// Check to load surrounding chunks
		
		//// Find current chunk
		xchunk = (int) ((Global.Instance().player.location.x / Global.Instance().tileSize) / Global.Instance().chunkSize);
		ychunk = (int) ((Global.Instance().player.location.y / Global.Instance().tileSize) / Global.Instance().chunkSize);
		
		// Reset each chunk state
		for (MapChunk c : chunks.values()) {
			c.unload = true;
			c.Update(elapsed);
		}
		
		addChunk(xchunk, ychunk);
		addChunk(xchunk - 1, ychunk - 1);
		addChunk(xchunk - 1, ychunk);
		addChunk(xchunk, ychunk - 1);
		addChunk(xchunk + 1, ychunk + 1);
		addChunk(xchunk + 1, ychunk);
		addChunk(xchunk, ychunk + 1);
		addChunk(xchunk - 1, ychunk + 1);
		addChunk(xchunk + 1, ychunk - 1);
	}

	public void addChunk(int x, int y){
		
		Vector2 key = new Vector2(x,y);
		if (chunks.containsKey(key)) {
			chunks.get(key).unload = false;
		} else {
			MapChunk c = new MapChunk(x, y);
			chunks.put(key, c);
		}
	}
	
	/** The NPC will attempt to get into range of the players radius.*/
	private void doAI(PlayerCharacter b) {
		// Simple straight pathing
		float distX = (Global.Instance().player.location.x - b.location.x);
		float distY = (Global.Instance().player.location.y - b.location.y);
		int dist = (int) Math.sqrt(distX*distX + distY*distY);
		
		if (dist > (b.range + Global.Instance().player.radius)) {
			if (Global.Instance().player.location.x > b.location.x + 0.5)
				b.xSpeed += 0.05;
			else if (Global.Instance().player.location.x < b.location.x - 0.5)
				b.xSpeed -= 0.05;
			if (Global.Instance().player.location.y > b.location.y + 0.5)
				b.ySpeed += 0.05;
			else if (Global.Instance().player.location.y < b.location.y - 0.5)
				b.ySpeed -= 0.05;
		}
	}

	@Override
	public void Draw(Graphics graphics) {
		// Draw the tile map
		/* Note: We assume that the TileType Enum value reflects the
		 * actual position of the tile relative to the tile-sheet.  This
		 * does not account for multi-line tile-sheets, but could be incorporated
		 * using modulus.  Maybe bad?
		 */

        // Check if there is enough tiles????
        if (numVerticalTiles > tileMap.getNumTilesY())
        	numVerticalTiles = tileMap.getNumTilesY();
        if (numHorizontalTiles > tileMap.getNumTilesX())
        	numHorizontalTiles = tileMap.getNumTilesX();
        
        // Draw each chunk
        //for (MapChunk c : chunks.values()) {
        //	c.Draw(graphics);
        //}
        
        Object[] cs = chunks.values().toArray();
        for (int i=0; i < cs.length; i++) {
        	((MapChunk)cs[i]).Draw(graphics);
        }
        
        
        /*  Draw Drawables  */
        for (DrawableObject d : drawableObjects) {
        	d.Draw(graphics);
        	
        	// Check if the drawable is a player Character
        	if (d.drawableType == DrawableType.PlayerCharacter) {
        		PlayerCharacter p = (PlayerCharacter)d;
        		
        		// Don't draw self!
	        	if (p.name != Global.Instance().username)
	        		graphics.drawString(p.name,
	            			(int)(p.location.x - Global.Instance().camera.getLocation().x),
	            			(int)(p.location.y - Global.Instance().camera.getLocation().y));
        	}
        }
        
        
        // Draw test stuff
        graphics.drawString("Chunk: " + Integer.toString(xchunk) + " x " + Integer.toString(ychunk), 200, 100);
	}
	
	public boolean isLoaded() {
		return loaded;
	}
	public boolean isLoading() {
		return loading;
	}
}
